// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2024-07-17

package src

import (
	"context"
	"errors"
	"fmt"
	"gitee.com/go-libs/config"
	"gitee.com/go-libs/log"
	gr "github.com/go-redis/redis/v8"
	"time"
)

// Connector
// is a singleton instance used to manage redis connection.
var Connector ConnectorManager

// Alias for redis/v8 types.

type (
	Client     = gr.Client
	Connection = gr.Conn
	Cmd        = gr.Cmd
)

type (
	// ConnectorManager
	// is an interface used to manage redis connection.
	ConnectorManager interface {
		// Do
		// send a command directly.
		Do(ctx context.Context, args ...any) (*Cmd, error)

		// GetClient
		// returns a redis client.
		GetClient() (client *Client, err error)

		// GetConfig
		// returns a config component.
		GetConfig() *Config

		// GetConnection
		// acquire a redis connection from pool.
		GetConnection(ctx context.Context) (connection *Connection, err error)

		// GetError
		// returns a error on connector.
		GetError() error
	}

	connector struct {
		client     *gr.Client
		config     *Config
		configFile config.Resource
		err        error
	}
)

func (o *connector) Do(ctx context.Context, args ...any) (cmd *Cmd, err error) {
	if err = o.err; err != nil {
		log.Errorfc(ctx, `[redis] failed to do: args=%v, error="%v"`, args, err)
		return
	}

	// Send command.
	cmd = o.client.Do(ctx, args...)

	if err = cmd.Err(); errors.Is(err, gr.Nil) {
		err = nil
	}

	if err != nil {
		log.Errorfc(ctx, `[redis] failed to do: args=%v, error="%v"`, args, err)
	} else {
		log.Infofc(ctx, `[redis] succeed to do: args=%v`, args)
	}
	return
}

func (o *connector) GetClient() (client *Client, err error) {
	if err = o.err; err == nil {
		client = o.client
	}
	return
}

func (o *connector) GetConfig() *Config {
	return o.config
}

func (o *connector) GetConnection(ctx context.Context) (connection *Connection, err error) {
	if err = o.err; err == nil {
		connection = o.client.Conn(ctx)
	}
	return
}

func (o *connector) GetError() error {
	return o.err
}

func (o *connector) build() {
	// Required options.
	opt := &gr.Options{
		Network:  o.config.Network,
		Addr:     fmt.Sprintf(`%s:%d`, o.config.Host, o.config.Port),
		Password: o.config.Password,
		DB:       o.config.Database,
	}

	// Optional options.
	opt.OnConnect = func(ctx context.Context, _ *Connection) error {
		log.Infofc(ctx, "[redis] connect to %s://%s:%d", o.config.Network, o.config.Host, o.config.Port)
		return nil
	}

	if o.config.MaxActive > 0 {
		opt.PoolSize = o.config.MaxActive
	}

	if o.config.MinIdle > 0 {
		opt.MinIdleConns = o.config.MinIdle
	}

	if o.config.MaxLifetime > 0 {
		opt.IdleTimeout = time.Duration(o.config.MaxLifetime) * time.Second
	}

	// Dial timeout.
	// Default: 5 seconds.
	if o.config.DialTimeout > 0 {
		opt.DialTimeout = time.Duration(o.config.DialTimeout) * time.Second
	}

	// Read timeout.
	// Default: 3 seconds.
	if o.config.ReadTimeout > 0 {
		opt.ReadTimeout = time.Duration(o.config.ReadTimeout) * time.Second
	}

	// Write timeout.
	// Default: 3 seconds.
	if o.config.WriteTimeout > 0 {
		opt.WriteTimeout = time.Duration(o.config.WriteTimeout) * time.Second
	}

	// Build.
	o.client = gr.NewClient(opt)
}

func (o *connector) init() *connector {
	o.config = &Config{}
	o.configFile = config.Seek(configFiles...)

	// Scan
	// config fields from file resource then build connection pool.
	o.err = o.configFile.ScanYaml(o.config)
	o.build()
	return o
}
